package com.foo.designpattern.flyweight.ComplexFlyweight;

import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.List;

/**
 * com.foo.designpattern.flyweight.ComplexFlyweight
 * Created with IntelliJ IDEA.
 * Created by jiang on 2016-05-24 10:09.
 *
 * @Description 测试类：享元模式(Flyweight)--复合享元模式
 * 享元模式：Flyweight在拳击比赛中指最轻量级，即"羽量级"，这里翻译为"享元模式"是因为这样更能反映模式的用意。
 * 享元模式是对象的结构模式，享元模式以共享的方式高效地支持大量的细粒度对象。
 * <p/>
 * 享元对象的结构：
 * 享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的消耗。
 * 享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
 * 一个内蕴状态是存储在享元对象内部的，并且不会随环境的改变而有所不同。因此，一个享元可以具有内蕴状态并可以共享。
 * 一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存，并在享元对象被创建后，
 * 在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态，它们是相互独立的。
 * 享元模式可以分成单纯享元模式和复合享元模式两种形式。
 * <p/>
 * 本例是复合享元模式
 * 在单纯享元模式中，所有的享元对象都是单独享元对象，也就是说都是可以直接共享的。
 * 如果将一些单纯享元使用合成模式加以复合，形成复合享元对象。
 * 这样的复合享元对象本身并不能共享，但是它们可以分解成单纯享元对象，而后者则可以共享。
 * <p/>
 * 复合享元模式所涉及的角色如下：
 * 1、抽象享元(Flyweight)角色：给出一个抽象接口，以规定出所有具体享元角色需要实现的方法；
 * 2、具体享元(ConcreteFlyweight)角色：实现抽象享元角色所规定出的接口。如果有内蕴状态的话，必须负责为内蕴状态提供存储空间；
 * 3、复合享元(ConcreteCompositeFlyweight)角色：复合享元角色所代表的对象是不可以共享的，但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合，
 * 复合享元角色又称作不可共享的享元对象；
 * 4、享元工厂(FlyweightFactory)角色：本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。
 * 当一个客户端调用一个享元对象的时候，享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。
 * 如果已经有了，享元工厂角色就应当提供这个已有的享元对象；
 * 如果系统中没有一个适当的享元对象的话，享元工厂角色就应当创建一个合适的享元对象。
 * <p/>
 * 享元模式的优点：
 * 1、享元模式大幅度的降低了内存中对象的数量。
 * 享元模式的缺点：
 * 1、享元模式使得系统变得更加复杂。为了使对象可以共享，需要将一些状态外部化，这使得程序的逻辑复杂化；
 * 2、享元模式将享元对象的状态外部化，而读取外部状态使得运行时间稍微变长。
 * <p/>
 * 享元模式的应用场景：
 * 享元模式在一般的项目开发中并不常用，而是常常应用于系统底层的开发，以便解决系统的性能问题。
 * 比如：java中的String类型就是使用了享元模式。
 * 系统中满足以下条件才建议使用享元模式：
 * 1、一个系统中存在这大量的细粒度对象；
 * 2、这些细粒度对象耗费了大量的内存；
 * 3、这些细粒度对象的状态中的大部分都可以外部化；
 * 4、这些细粒度对象可以按照内蕴状态分成很多的组，当把外蕴状态从对象中剔除时，每一个组都可以仅用一个对象代替；
 * 5、软件系统不依赖于这些对象的身份，换言之，这些对象可以是不可分辨的。
 * 满足以上的这些条件的系统可以使用享元模式。最后，使用享元模式需要维护一个记录了系统已有的所有享元的哈希表，也成为对象池，
 * 而这也需要耗费一定的资源。因此，应当在有足够多的享元实例可供共享时才值得使用享元模式。
 * <p/>
 * 享元模式与单例模式的区别：
 * 享元模式与单例模式都是保证类的实例的唯一性；
 * 但单例模式的实现方法是：在类的内部，即在构造方法中或静态的getInstance方法中，进行判断，若实例存在，则直接返回，不进行创建；
 * 享元模式的实现方式是：
 * 每次要用到此实例时，先去此HashTable中获取，如获取为空，则生成实例，且将类的实例放在一人HashTable中；若获取不为空，则直接用此实例。
 */

public class ComplexFlyweightTestCase extends TestCase {
    public void testComplexFlyweight() {
        System.out.println("= = = = ↓↓↓复合享元模式，测试开始↓↓↓ = = = =");

        /**
         * 从运行结果可以看出，一个符合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的。
         * 即外蕴状态都等于 Composite Call.
         * 从运行结果可以看出，一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的。
         * 即内蕴状态分别为 b、c、a.
         * 从运行结果可以看出，单纯享元对象是可以共享的。即用相同的对象state通过工厂分别两次创建出的对象是同一个对象。
         */

        List<Character> compositeState = new ArrayList<Character>();
        compositeState.add('a');
        compositeState.add('b');
        compositeState.add('c');
        compositeState.add('a');
        compositeState.add('b');

        FlyweightFactory flyFactory = new FlyweightFactory();
        Flyweight compositeFly1 = flyFactory.factory(compositeState);
        Flyweight compositeFly2 = flyFactory.factory(compositeState);
        compositeFly1.operation("Composite Call");

        System.out.println("复合享元模式是否可以共享对象：" + (compositeFly1 == compositeFly2));
        Character state = 'a';
        Flyweight fly1 = flyFactory.factory(state);
        Flyweight fly2 = flyFactory.factory(state);
        System.out.println("单纯享元模式是否可以共享对象：" + (fly1 == fly2));

        System.out.println("= = = = ↑↑↑复合享元模式，测试结束↑↑↑ = = = =");
    }
}
